package jxerializeml;

/**
 * <p>
 *   Contains classes of all primitive types and also bounding
 *   boxes of these classes. It also contains a String.
 * </p>
 * <p>
 *   It might be used for determining if some object is primitive
 *   (or can be triggered as a primitive), and what is it's type
 *   if so.
 * </p>
 * 
 * @author Tomáš Zíma
 */
public enum PrimitiveType {
	// Decimal numbers
	LONG(new Class<?>[]{Long.class, long.class}, true),
	INTEGER(new Class<?>[]{Integer.class, int.class}, true),
	SHORT(new Class<?>[]{Short.class, short.class}, true),
	BYTE(new Class<?>[]{Byte.class, byte.class}, true),
	
	// Floating point numbers
	DOUBLE(new Class<?>[]{Double.class, double.class}, true),
	FLOAT(new Class<?>[]{Float.class, float.class}, true),
	
	// Text representation
	STRING(new Class<?>[]{String.class}, false),
	CHARACTER(new Class<?>[]{Character.class, char.class}, false),
	
	// Boolean logic
	BOOLEAN(new Class<?>[]{Boolean.class, boolean.class}, false),
	
	// Not a primitive
	NON_PRIMITIVE(new Class<?>[]{}, false);
	
	/** Class of primitive type and it's bounding box. */
	private Class<?>[]	classes = null;
	private boolean		numeric = false;
	
	/**
	 * @param classes
	 *     Class of primitive type and it's bounding box.
	 *   
	 * @param numeric
	 *     True if type is numeric.
	 */
	private PrimitiveType(Class<?>[] classes, boolean numeric) {
		this.classes = classes;
		this.numeric = numeric;
	}
	
	/**
	 * @return
	 *     Classes of primitive type (if exists) and it's
	 *     bounding box.
	 */
	public Class<?>[] getClasses() {
		return classes;
	}
	
	/**
	 * @return
	 *     True if type is numeric.
	 */
	public boolean isNumeric() {
		return numeric;
	}
	
	/**
	 * @param type
	 *     Type to be checked.
	 *   
	 * @return
	 *     Checks if it's primitive type. If so, corresponding
	 *     PrimitiveType value will be returned. In other case,
	 *     the result will be PrimitiveType.NON_PRIMITIVE.
	 */
	public static PrimitiveType getPrimitiveType(Class<?> type) {
		for (PrimitiveType primitiveType : PrimitiveType.values()) {
			for (Class<?> primitiveClass : primitiveType.getClasses()) {
				if (primitiveClass.equals(type)) {
					return primitiveType;
				}
			}
		}
		
		return PrimitiveType.NON_PRIMITIVE;
	}
	
	/**
	 * Checks if given class is primitive (or bounding box of
	 * primitive).
	 * 
	 * @param type
	 *     Type to be checked.
	 *     
	 * @return
	 *     True if given class is primitive.
	 */
	public static boolean isPrimitive(Class<?> type) {
		if (!type.isPrimitive()) {
			if (getPrimitiveType(type) != PrimitiveType.NON_PRIMITIVE) {
				return true;
			}
			else {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * Checks if given class is numeric.
	 * 
	 * @param type
	 *     Type to be checked.
	 *     
	 * @return
	 *     True if it's numeric type.
	 */
	public static boolean isNumeric(Class<?> type) {
		return getPrimitiveType(type).isNumeric();
	}
	
	/**
	 * Returns value of any numeric object casted to the double.
	 * 
	 * @param object
	 *     Object to get the value from.
	 *     
	 * @return
	 *     Value of the object casted to the double.
	 *     
	 * @throw NumberFormatException
	 *     Given object is not a number.
	 */
	public static double getNumericValue(Object object) {
		switch (getPrimitiveType(object.getClass())) {
			case LONG:		return Long.valueOf(object.toString());
			case INTEGER:	return Integer.valueOf(object.toString());
			case DOUBLE:	return Double.valueOf(object.toString());
			case FLOAT:		return Float.valueOf(object.toString());
			case SHORT:		return Short.valueOf(object.toString());
			case BYTE:		return Byte.valueOf(object.toString());
			
			case STRING:
			case CHARACTER:
			case BOOLEAN:
			case NON_PRIMITIVE:
			default:
				throw new NumberFormatException("Given object isn't a number.");
		}
	}
}
